#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/stat.h>

#include "mysignal.h"

// 设备名称
#define MYCDEV_NAME "mycdev_simple"
// 设备类名称
#define MYCDEV_CLASS "mycdev_class"
// 设备数量
#define CDEV_NUM 1

// 打印格式
#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__

// 设备类
static struct class *mycdev_class;
// 所有设备的数组
static struct cdev cdevs[CDEV_NUM];
static struct device devices[CDEV_NUM];

// 设备号
static dev_t dev_num;
// 模式 -1时为第一种模式，1时为第二种模式
static int mode = MODE_1;

// 保存一个读入的字符串
static char mystr[1024] = {0};

static int strLength(const char *s);
static void reverse_string(char *str);
static int mycdev_open(struct inode *inode, struct file *file);
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len, loff_t *pos);
static ssize_t mycdev_write(struct file *, const char __user *buff, size_t len, loff_t *pos);
static int mycdev_release(struct inode *inode, struct file *file);
static long mycdev_ioctl(struct file *fd, unsigned int cmd, unsigned long b);

static ssize_t mycdev_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count);
static ssize_t mycdev_show(struct device *dev, struct device_attribute *attr, char *buffer);

static DEVICE_ATTR(mydev, S_IWUSR | S_IRUGO, mycdev_show, mycdev_store);

static struct file_operations op_f = {
    .read = mycdev_read,
    .write = mycdev_write,
    .open = mycdev_open,
    .release = mycdev_release,
    .unlocked_ioctl = mycdev_ioctl};

// 模块初始化函数
static int __init simple_chardev_init(void)
{
    int i, retVal;
    /*设备号*/
    dev_t curr_devnum;

    /*请求内核分配设备号*/
    retVal = alloc_chrdev_region(&dev_num, 0, CDEV_NUM, MYCDEV_CLASS);
    if (retVal != 0)
    {
        pr_err("alloc_chrdev_region error %d\n", retVal);
        return retVal;
    }

    // 为字符设备创建设备类,在/sys/class中可见
    mycdev_class = class_create(THIS_MODULE, MYCDEV_CLASS);

    /*创建字符设备结构并注册到内核*/
    for (i = 0; i < CDEV_NUM; i++)
    {
        /*初始化字符设备*/
        cdev_init(&cdevs[i], &op_f);
        cdevs[i].owner = THIS_MODULE;

        /*设置设备号*/
        curr_devnum = MKDEV(MAJOR(dev_num), MINOR(dev_num) + i);
        /*添加设备到内核*/
        retVal = cdev_add(&cdevs[i], curr_devnum, 1);
        pr_info("cdev_add retVal %d\n", retVal);

        /*创建设备节点*/
        devices[i] = *(device_create(mycdev_class, NULL, curr_devnum, NULL, MYCDEV_NAME "%d", i));
        pr_info("device_create %p\n", &devices[i]);

        /*创建设备属性，生成文件节点*/
        if (retVal = device_create_file(&devices[i], &dev_attr_mydev))
        {
            pr_err("create attr failed! retVal %d\n", retVal);
        }
    }
    return retVal;
}

static void __exit simple_chardev_exit(void)
{
    dev_t _devnum;
    int i;

    for (i = 0; i < CDEV_NUM; i++)
    {
        _devnum = MKDEV(MAJOR(dev_num), MINOR(dev_num) + i);
        device_destroy(mycdev_class, _devnum);
        cdev_del(&(cdevs[i]));
    }
    class_destroy(mycdev_class);

    unregister_chrdev_region(dev_num, CDEV_NUM);
    printk("simple_chardev_exit\n");
    return;
}
/*通过运行cat读取时所执行的函数*/
static ssize_t mycdev_show(struct device *dev, struct device_attribute *attr, char *buffer)
{
    ssize_t retVal;
    pr_info("call mycdev_show\n");

    int len = strLength(mystr) + 1;
    if (mode == MODE_1)
    {
        // read回原字符串
        retVal = snprintf(buffer, len, "%s", mystr);
        return retVal;
    }
    else
    {
        // read回倒序字符串
        reverse_string(mystr);
        retVal = snprintf(buffer, len, mystr);
        // 还原
        reverse_string(mystr);
        return retVal;
    }
}

/*通过运行echo命令所执行的函数*/
static ssize_t mycdev_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count)
{

    pr_info("call mycdev_store\n");
    memset(mystr, 0, sizeof(mystr));
    // echo一个字符串给设备属性
    if (!sprintf(mystr, "%s", buffer))
    {
        pr_err("input error!\n");
        return -1;
    };
    pr_info("input data: %s", mystr);
    return count;
}

/*打开设备*/
static int mycdev_open(struct inode *inode, struct file *file)
{
    /*打印一条消息到内核日志*/
    pr_info("call mycdev_open\n");
    return 0;
}

/*读操作*/
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len, loff_t *pos)
{
    int count, retVal;
    pr_info("call mycdev_read\n");
    count = strLength(mystr);

    if (mode == MODE_1)
    {
        reverse_string(mystr);
        retVal = copy_to_user(buff, mystr, count + 1);
        // 还原
        reverse_string(mystr);
    }
    else
    {
        retVal = copy_to_user(buff, mystr, count + 1);
    }
    if (retVal == -EFAULT)
    {
        pr_err("mycdev_read error\n");
        return -EFAULT;
    }
    return count + 1;
}

/*写操作*/
static ssize_t mycdev_write(struct file *file, const char __user *buff, size_t len, loff_t *pos)
{
    int retVal;

    pr_info("call mycdev_write\n");
    memset(mystr, 0, sizeof(mystr));
    retVal = copy_from_user(mystr, buff, len);
    if (retVal == -EFAULT)
    {
        pr_err("mycdev_write error\n");
        return -EFAULT;
    }
    pr_info("recv %s\n", mystr);

    return len;
}
/*ioctl*/
static long mycdev_ioctl(struct file *fd, unsigned int cmd, unsigned long b)
{
    pr_info("call mycdev_ioctl\n");

    pr_info("cmd type=%c\t nr=%d\t dir=%d\t size=%d\n", _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_DIR(cmd), _IOC_SIZE(cmd));
    switch (cmd)
    {
    case PRINT_MODE_1:
        pr_info("print mode switch to <normal>");
        mode = MODE_1;
        break;
    case PRINT_MODE_2:
        pr_info("print mode switch to <reverse>");
        mode = MODE_2;
    default:
        pr_err("err happend when switch mode.");
        break;
    }
    return 0;
}

/*关闭文件*/
static int mycdev_release(struct inode *inode, struct file *file)
{
    pr_info("call mycdev_release\n");
    return 0;
}

/*计算字符串长度*/
static int strLength(const char *s)
{
    int len;
    len = 0;
    while ((*s) != 0)
    {
        s++;
        len++;
    }
    return len;
}
/*字符串倒序*/
static void reverse_string(char *str)
{
    int len = strLength(str);
    char *left = str;
    char *right = str + (len - 1);
    char temp;
    while (left < right)
    {
        temp = *left;
        *left = *right;
        *right = temp;
        left++;
        right--;
    }
}

module_init(simple_chardev_init);
module_exit(simple_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxx");
MODULE_DESCRIPTION("内核描述");